﻿using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using WinAction.UI;

namespace WinAction.Hook
{
	public static class ClipboardHook
	{

		[DllImport("user32.dll")]
		private static extern bool AddClipboardFormatListener(IntPtr hwnd);

		[DllImport("user32.dll")]
		private static extern bool RemoveClipboardFormatListener(IntPtr hwnd);

		private static ClipboardUtilWin win = null;

		private static readonly object locker = new object();

		private static ConcurrentDictionary<string, Action<ClipboardDataObject>> eventDic = new ConcurrentDictionary<string, Action<ClipboardDataObject>>();


		/// <summary>
		/// 开始监听系统剪贴板的变化
		/// </summary>
		internal static void StartHook(IntPtr winHandle)
		{
			AddClipboardFormatListener(winHandle);
		}

		/// <summary>
		/// 停止监听系统剪贴板的变化
		/// </summary>
		internal static void StopHook(IntPtr winHandle)
		{
			RemoveClipboardFormatListener(winHandle);
		}

		/// <summary>
		/// 注册一个剪贴板监听事件
		/// </summary>
		/// <param name="clipChangeEvent">剪贴板改变事件</param>
		/// <returns>事件ID</returns>
		public static string RegisterClipboardChangeEvent(Action<ClipboardDataObject> clipChangeEvent)
		{
			if (clipChangeEvent == null)
			{
				throw new NullReferenceException("待注册的键盘事件不可为null");
			}
			string id = Guid.NewGuid().ToString("N");
			lock (locker)
			{
				if (eventDic.TryAdd(id, clipChangeEvent))
				{
					if (win == null)
					{
						win = new ClipboardUtilWin();
						win.Show();
					}
					return id;
				}
			}
			return null;
		}

		/// <summary>
		/// 取消注册剪贴板监听事件
		/// </summary>
		/// <param name="eventId">事件DI</param>
		/// <returns>是否取消成功</returns>
		public static bool UnRegisterClipboardChangeEvent(string eventId)
		{
			Action<ClipboardDataObject> clipChangeEvent;
			bool res = eventDic.TryRemove(eventId, out clipChangeEvent);
			lock (locker)
			{
				if (eventDic.Count <= 0 && win != null)
				{
					win.Close();
					win = null;
				}
			}

			return res;
		}

		/// <summary>
		/// 触发剪贴板变化事件
		/// </summary>
		internal static void ClipBoardChanged()
		{
			foreach (var eventPair in eventDic)
			{
				eventPair.Value?.Invoke(new ClipboardDataObject(Clipboard.GetDataObject()));
			}
		}

		internal static string GetDesc(this ClipboardDataFormat cdf)
		{
			Type enumType = cdf.GetType();
			string name = Enum.GetName(enumType, cdf);
			DescriptionAttribute desc = enumType.GetField(name).GetCustomAttribute<DescriptionAttribute>();
			return desc.Description;
		}
	}


	public class ClipboardDataObject : IDataObject
	{
		private readonly IDataObject _proxyDataObject;

		public ClipboardDataObject(IDataObject proxy)
		{
			_proxyDataObject = proxy;
		}

		public object GetData(string format, bool autoConvert) => _proxyDataObject.GetData(format, autoConvert);
		public object GetData(string format) => _proxyDataObject.GetData(format);
		public object GetData(Type format) => _proxyDataObject.GetData(format);
		
		public object GetData(ClipboardDataFormat format) => _proxyDataObject.GetData(format.GetDesc());

		public bool GetDataPresent(string format, bool autoConvert) =>
			_proxyDataObject.GetDataPresent(format, autoConvert);

		public bool GetDataPresent(string format) => _proxyDataObject.GetDataPresent(format);
		public bool GetDataPresent(Type format) => _proxyDataObject.GetDataPresent(format);
		public bool GetDataPresent(ClipboardDataFormat format) => _proxyDataObject.GetDataPresent(format.GetDesc());
		public string[] GetFormats(bool autoConvert) => _proxyDataObject.GetFormats(autoConvert);
		public string[] GetFormats() => _proxyDataObject.GetFormats();

		public void SetData(string format, bool autoConvert, object data) =>
			_proxyDataObject.SetData(format, autoConvert, data);

		public void SetData(string format, object data) => _proxyDataObject.SetData(format, data);
		public void SetData(Type format, object data) => _proxyDataObject.SetData(format, data);
		public void SetData(ClipboardDataFormat format, object data) => _proxyDataObject.SetData(format.GetDesc(), data);
		public void SetData(object data) => _proxyDataObject.SetData(data);

		public string GetText() => GetData(ClipboardDataFormat.Text).ToString();

	}

	public enum ClipboardDataFormat
	{
		[Description("Text")]
		Text,
		[Description("UnicodeText")]
		UnicodeText,
		[Description("DeviceIndependentBitmap")]
		Dib,
		[Description("Bitmap")]
		Bitmap,
		[Description("EnhancedMetafile")]
		EnhancedMetafile,
		[Description("MetaFilePict")]
		MetafilePicture,
		[Description("SymbolicLink")]
		SymbolicLink,
		[Description("DataInterchangeFormat")]
		Dif,
		[Description("TaggedImageFileFormat")]
		Tiff,
		[Description("OEMText")]
		OemText,
		[Description("Palette")]
		Palette,
		[Description("PenData")]
		PenData,
		[Description("RiffAudio")]
		Riff,
		[Description("WaveAudio")]
		WaveAudio,
		[Description("FileDrop")]
		FileDrop,
		[Description("Locale")]
		Locale,
		[Description("HTML Format")]
		Html,
		[Description("Rich Text Format")]
		Rtf,
		[Description("CSV")]
		CommaSeparatedValue,
		[Description("System.String")]
		StringFormat,
		[Description("PersistentObject")]
		Serializable,
		[Description("Xaml")]
		Xaml,
		[Description("XamlPackage")]
		XamlPackage,
		[Description("ApplicationTrust")]
		ApplicationTrust,
		[Description("FileName")]
		FileName,
		[Description("FileNameW")]
		FileNameW,
	}

}
